package com.github.operationlog.command;

import com.github.operationlog.*;
import com.github.operationlog.output.OperationLogOutput;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 操作方法命令
 *
 * @author luke.luo
 */
@Slf4j
public abstract class OperationCommand {
    private String table;
    private Object target;
    private OperationLogOutput operationLogOutput;
    private Method findByIdMethod;

    MethodAttribute methodAttribute;

    OperationCommand(String table,
                     Object target,
                     MethodAttribute methodAttribute,
                     Method findByIdMethod,
                     OperationLogOutput operationLogOutput) {
        this.table = table;
        this.target = target;
        this.methodAttribute = methodAttribute;
        this.findByIdMethod = findByIdMethod;
        this.operationLogOutput = operationLogOutput;
    }

    public Object execute(Object[] args) {
        if (operationLogOutput == null) {
            log.warn("日志输出函数为空, 无须进行数据差异判断，method={}-{}",
                    methodAttribute.getTargetName(), methodAttribute.getName());
            return ReflectionUtils.invokeMethod(methodAttribute.getMethod(), target, args);
        }

        if (CollectionUtils.isEmpty(methodAttribute.getEntityFieldList())) {
            return ReflectionUtils.invokeMethod(methodAttribute.getMethod(), target, args);
        }

        return execute0(target, methodAttribute.getMethod(), args, parseEntityId(args));
    }

    abstract Object execute0(Object target, Method method, Object[] args, Object entityId);

    Object findById(Object entityId) {
        return entityId == null ? null :
                ReflectionUtils.invokeMethod(findByIdMethod, target, entityId);
    }

    /**
     * 从方法参数列表中，解析实体主键ID值
     *
     * @param args 参数列表
     * @return 实体主键ID值
     */
    private Object parseEntityId(Object[] args) {
        if (!methodAttribute.isEntityParam()) {
            // 非实体参数，
            return args[methodAttribute.getPrimaryKeyPosition()];
        }

        Object entity = args[0];
        for (EntityField entityField : methodAttribute.getEntityFieldList()) {
            if (!entityField.isIdentity()) {
                continue;
            }

            Field field = ReflectionUtils.findField(entity.getClass(), entityField.getName());
            if (field != null) {
                ReflectionUtils.makeAccessible(field);
                return ReflectionUtils.getField(field, entity);
            }
        }

        return null;
    }

    /**
     * 将实体字段的值创建为{@link EntityValue}对象返回（只设置新值）
     *
     * @param entityId 实体对象ID
     * @param args 操作实体方法执行的参数
     * @return {@link EntityValue}
     */
    List<EntityValue> buildEntityNewValue(Object entityId, Object[] args) {
        if (methodAttribute.isEntityParam()) {
            // 实体参数
            return buildEntityNewValue(args[0]);
        }

        // 非实体参数，需要根据ID查询实体
        Object entity = ReflectionUtils.invokeMethod(findByIdMethod, target, entityId);
        return entity != null ? buildEntityNewValue(entity) : null;
    }

    /**
     * 将实体字段的值创建为{@link EntityValue}对象返回（只设置新值）
     *
     * @param entity 实体对象
     * @return {@link EntityValue}
     */
    private List<EntityValue> buildEntityNewValue(Object entity) {
        Map<String, Object> entityValueMap = convertMap(entity);
        return entityValueMap.isEmpty() ? Collections.emptyList() :
                methodAttribute.getEntityFieldList().stream()
                .filter(entityField -> entityValueMap.containsKey(entityField.getName()))
                .map(entityField -> {
                    Object value = entityValueMap.get(entityField.getName());
                    return new EntityValue(
                            entityField.getName(),
                            entityField.getNote(),
                            null, value
                    );
                })
                .collect(Collectors.toList());
    }

    /**
     * 将实体字段的值创建为{@link EntityValue}对象返回（只设置旧）
     *
     * @param entity 实体对象
     * @return {@link EntityValue}
     */
    List<EntityValue> buildEntityOldValue(Object entity) {
        Map<String, Object> entityValueMap = convertMap(entity);
        if (entityValueMap.isEmpty()) {
            return Collections.emptyList();
        }

        return methodAttribute.getEntityFieldList().stream()
                        .filter(entityField -> entityValueMap.containsKey(entityField.getName()))
                        .map(entityField -> {
                            Object value = entityValueMap.get(entityField.getName());
                            return new EntityValue(
                                    entityField.getName(),
                                    entityField.getNote(),
                                    value, null
                            );
                        })
                        .collect(Collectors.toList());
    }

    /**
     * 过滤出旧实体和新实体字段值不一样的字段，封装为{@link EntityValue}对象返回
     *
     * @param oldEntity 旧实体
     * @param newEntity 新实体
     * @return {@link EntityValue}
     */
    private List<EntityValue> filterDiffEntityFieldValue(Object oldEntity, Object newEntity) {
        // 旧值表,key->字段名,value->字段值
        Map<String, Object> oldValueTable = convertMap(oldEntity);

        // 新值表,key->字段名,value->字段值
        Map<String, Object> newValueTable = convertMap(newEntity);

        return methodAttribute.getEntityFieldList().stream()
                .map(entityField -> {
                    Object newValue = newValueTable.get(entityField.getName());
                    if (newValue == null) {
                        return null;
                    }

                    Object oldValue = oldValueTable.get(entityField.getName());
                    if (oldValue != null && StringUtils.equals(newValue.toString(), oldValue.toString())) {
                        return null;
                    }

                    return new EntityValue(entityField.getName(), entityField.getNote(), oldValue, newValue);
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    // 实体转换为Map，key->字段名，value->字段值
    private Map<String, Object> convertMap(Object entity) {
        if (entity == null) {
            return Collections.emptyMap();
        }

        Map<String, Object> fieldValueMap = new HashMap<>();

        List<Field> fieldList = Arrays.asList(entity.getClass().getDeclaredFields());
        Class superclass = entity.getClass().getSuperclass();
        while (superclass != null && superclass != Object.class) {
            fieldList.addAll(Arrays.asList(superclass.getDeclaredFields()));
            superclass = superclass.getSuperclass();
        }

        for (Field field : fieldList) {
            try {
                ReflectionUtils.makeAccessible(field);
                Object value = ReflectionUtils.getField(field, entity);
                if (value != null) {
                    fieldValueMap.put(field.getName(), value);
                }
            } catch (Exception e) {
                // do-nothing
            }
        }
        return fieldValueMap;
    }

    void insertLog(Object entityId, Object oldEntity, Object newEntity) {
        try {
            List<EntityValue> diffEntityFieldValueList = filterDiffEntityFieldValue(oldEntity, newEntity);
            insertLog(entityId, diffEntityFieldValueList);
        } catch (Exception e) {
            // do-nothing
        }
    }

    /**
     * 输出操作日志
     *
     * @param entityId 实体ID
     * @param entityValueList 操作日志内容
     */
    void insertLog(Object entityId, List<EntityValue> entityValueList) {
        if (CollectionUtils.isEmpty(entityValueList)) {
            return;
        }

        try {
            OperationLogData logData = new OperationLogData();
            logData.setTable(table);
            logData.setEntityId(entityId);
            logData.setType(methodAttribute.getType());
            logData.setBusinessName(methodAttribute.getBusinessName());
            logData.setContent(entityValueList);
            operationLogOutput.process(logData);
        } catch (Exception e) {
            // do-nothing
        }
    }
}